
--[[
command menu data

menu = {
	name = 'name',
	items = {
		[1] = {...},
		[2] = {...},
		...
		[n] = {...},
	}
}

item = {
	name = 'name',
	[command = <command>,
	or
	parameter = <parameter or { <parameters vector> }>,]
	[submenu = {<menu table>}]
}
--]]

local base = _G

module('CommandMenu')

__index = base.getfenv()

local fsm = base.require('fsm')
local utils = base.require('utils')

local gettext = base.require("i_18n")
local _ = gettext.translate

local Gui = base.require("dxgui")
local Static = base.require("Static")
local Window = base.require("Window")
local StaticList = base.require("StaticList")
local StaticMenu = base.require("StaticMenu")
local Panel = base.require("Panel")
local Skin  = base.require('Skin')
local Size = base.require("Size")
local Color = base.require("Color")
local Text = base.require("Text")
local Bkg = base.require("Bkg")

ON_OPEN = 0
ON_CLOSE = -1
ON_HOME = -2

ON_PREVIOUS = 11
ON_EXIT = 12

menuWidth = 280

function eventCallback(ptr, event, ...)
	local self = list[ptr]
	local handler = handlers[event]
	handler(self, ...)
end

function new(self, data, mainMenu, width, height)
	local newCommandMenu = {}
	base.setmetatable(newCommandMenu, self)	
	self.__index = self
	newCommandMenu:init(data, mainMenu, width, height)
	return newCommandMenu
end

function init(self, data, mainMenu, parent)
		
	self.level = 0
	self.path = {}
	self.messageDisappearTimeQueue = {}
	self.funcRef = nil
	
	local x, y, width, height = parent:getBounds()
	
	local containerSkin = {}
	local container = Panel.new('CommandMenu.container')
	do
		container:setSkin(containerSkin)
		container:setBounds(0, 0, width, height)
		self.container = container
	end
	
	local menuCaptionHeight = 20
-- Menu Headline and Selection:
	local font = base.skinPath..'../fonts/DejaVuLGCSansCondensed-Bold.ttf' -- default:  DejaVuLGCSansMono.ttf
	local textSkin = Skin.staticSkin()
	textSkin.skinData.params.textWrapping = true
	textSkin.skinData.states.released[1].text.font = font
	textSkin.skinData.states.released[1].text.fontSize = 18;  -- default: 12
	textSkin.skinData.states.released[1].text.color = Color.white()
	textSkin.skinData.states.released[1].text.shadowColor = Color.black()
	textSkin.skinData.states.released[1].text.shadowOffset = Size.new(1, 1)
	textSkin.skinData.states.released[1].bkg = Bkg.singleLineBorder(Color.new(0, 0.5, 0))	
	
	do
		local menuPanel = Panel.new('CommandMenu.menuPanel')
		menuPanel:setSkin(containerSkin)
		menuPanel:setBounds(width - menuWidth, 0, menuWidth, height)
		container:insertWidget(menuPanel)
		self.menuPanel = menuPanel
-- Radio call Headline fontsize	
		do 
			local menuCaption = Static.new('CommandMenu.menuCaption')
			menuCaption:setSkin(textSkin)
			local menuCaptionSkin = menuCaption:getSkin()
			menuCaptionSkin.skinData.states.released[1].text.fontSize = 20; -- default: 15
			menuCaptionSkin.skinData.states.released[1].text.color = Color.white()
			menuCaptionSkin.skinData.states.released[1].text.shadowColor = Color.black()
			menuCaptionSkin.skinData.states.released[1].text.shadowOffset = Size.new(1, 1)
			menuCaption:setSkin(menuCaptionSkin)
			menuCaption:setBounds(0, 0, menuWidth, menuCaptionHeight)
			menuPanel:insertWidget(menuCaption)
			self.menuCaption = menuCaption
		end
		
		do
			local menuHeight = height - menuCaptionHeight
			local skin = {
				container = containerSkin,
				item = textSkin
			}
			local commandMenu = StaticMenu.new('CommandMenu.commandMenu', 0, menuCaptionHeight, menuWidth, menuHeight, 12, skin)
			menuPanel:insertWidget(commandMenu:getContainer())
			self.commandMenu = commandMenu
		end
	end
	self.menuPanel:setVisible(false)
	
	do
		local messageLogWidth = width - menuWidth
		local messageLog = StaticList.new('CommandMenu.messageLog', textSkin)
		local skin = {
			container = containerSkin,
			item = textSkin,
			popItem = StaticList.PopFrontItem
		}
		messageLog:setSkin(skin)
		messageLog:setBounds(0, 0, messageLogWidth, height)
		container:insertWidget(messageLog:getContainer())
		self.messageLog = messageLog
	end	
	
	if data then
		self:setData(data, mainMenu)
	end
	
	return self.container
end

function setData(self, data, mainMenu)
	--self.data = data
	local fsmData = {}
	self.fsmData = fsmData
	self:buildFsmData(fsmData, data, mainMenu)
	self.fsm = fsm:new(fsmData, self.onSymbol, self, 'menu')
end

function buildFsmData(self, fsmData, data, mainMenu)
	--base.print('CommandDialog.buildFsmData()')
	self.fsmData = fsmData
	self.mainMenu = mainMenu
	fsmData.init_state = 'Closed'
	fsmData.transitions = {
		['Closed'] = {
			[ON_OPEN] = {
					outSymbol = function(self)	
						--base.print('on open')
						self:clearMenu()
						self:enterMenu_( {name = self.mainMenu.name, submenu = self.mainMenu} )
					end,
					newState = self.mainMenu.name,
					stackOption = fsm.PUSH_TO_STACK
			},
		}
	}
	fsmData.initStateFreeTransitions = {
		[ON_HOME] = {
			outSymbol = function(self)
					--base.print('on home')
					self:clearMenu()
					self:enterMenu_( {name = self.mainMenu.name, submenu = self.mainMenu} )					
			end,
			newState = self.mainMenu.name,
			stackOption = fsm.PUSH_TO_STACK,
		},
		[ON_CLOSE] = {
			outSymbol = self.closeMenu_,
			newState = 'Closed'
		},
	}
	self.headItem = {name = mainMenu.name, submenu = mainMenu}
	self:buildMenuFsm_(fsmData.transitions, self.headItem)
end

function buildMenuFsm_(self, transitions, menu_item)
	transitions[menu_item.name] = {}
	for item_index, item in base.pairs(menu_item.submenu.items) do
		if item.submenu then
			transitions[menu_item.name][item_index] =
			{
				outSymbol = function(self)
					self:enterMenu_(item)
				end,
				newState = item.name,
				stackOption = fsm.PUSH_TO_STACK
			}
			self:buildMenuFsm_(transitions, item)
		elseif item.menuBuilder then
			transitions[menu_item.name][item_index] =
			{
				outSymbol = function(self)
					--base.print('item.menuBuilder(self) call')
					self:pushMenuItem_(item)
					item.menuBuilder(self)
				end,
				newState = item.name,
				stackOption = fsm.PUSH_TO_STACK
			}
		else
			transitions[menu_item.name][item_index] =
			{
				outSymbol = function(self)
					--base.print('select item '..item.name)
					self:selectCommand_(item)
				end,
				newState = 'Closed'
			}
		end			
	end

	transitions[menu_item.name][ON_PREVIOUS] = {
		outSymbol = function(self)
			self:popMenuItem_()
			local top = self.path[#self.path]
			if top.submenu then
				self:buildMenu_(top.submenu)
			elseif top.menuBuilder then
				top.menuBuilder(self)
			end
		end,
		newState = nil,
		stackOption = fsm.POP_FROM_STACK
	}
	
	transitions[menu_item.name][ON_EXIT] = {
		outSymbol = self.closeMenu_,
		newState = 'Closed',
		stackOption = fsm.CLEAR_STACK
	}
end

function buildMenuFsm(self, menu_item)
	self:buildMenuFsm_(self.fsmData.transitions, menu_item)
end

function setHandler(self, handler)
	self.handler = handler
end

function clearLog_(self)
	self.messageLog:clear()
end

function release(self)
	fsm:reset()
	
	if self.funcRef ~= nil then
		base.timer.removeFunction(self.funcRef)
		self.funcRef = nil
	end
	
	base.assert(self.container ~= nil)
	self.container:destroy()
	self.container = nil
		self.menuPanel = nil
			self.menuCaption = nil
			self.commandMenu = nil
		self.messageLog = nil
end

local function onMenuShowHide(self, prev, cur)
	if self.handler then
		if not prev and cur then
			self.handler:onMenuShow(self)
		elseif prev and not cur then
			self.handler:onMenuHide(self)
		end
	end
end

function toggle(self, on)
	--base.print('toggle('..base.tostring(on)..')')
	onMenuShowHide(self, self:isMenuVisible(), on and self.menuPanel:isVisible())
	self.container:setVisible(on)
	return self.container:isVisible()
end

function toggleMenu(self, on)
	--base.print('toggleMenu('..base.tostring(on)..')')
	onMenuShowHide(self, self:isMenuVisible(), self.container:isVisible() and on)
	self.menuPanel:setVisible(on)
	return self.menuPanel:isVisible()
end

function isMenuVisible(self)
	--base.print('isVisible()')
	return self.container:getVisible() and self.menuPanel:isVisible()
end

function isLogEmpty(self)
	return self.messageLog:getContainer():getWidgetCount() == 0
end

function buildSelectionMenu(self, new_menu_name, new_menu_items, child_menu_item)
	local new_menu_item = {
		name = new_menu_name,
		submenu = {
			name = new_menu_name,
			items = {}
		}
	}
		
	--radio_commandMenu.fsm_data.transitions[new_menu_name] = {}

	for i, v in base.pairs(new_menu_items) do
		base.table.insert(	new_menu_item.submenu.items,
							{
								name 		= v.name,
								submenu		= child_menu_item.submenu,
								color		= v.color,
								command 	= v.command,
								parameter 	= v.parameter
							} )
	end
	--base.print('self:buildMenuFsm_(new_menu_item, parent_menu)')
	self:buildMenuFsm_(self.fsmData.transitions, new_menu_item)
	--base.print('self:buildMenuFsm_(child_menu_item, new_menu_item.submenu)')
	self:buildMenuFsm_(self.fsmData.transitions, child_menu_item)
	self:buildMenu_(new_menu_item.submenu)
end

function pushMenuItem_(self, menu_item)
	base.table.insert(self.path, menu_item)
	self.receiver = menu_item.receiver
	self.level = self.level + 1
end

function popMenuItem_(self)
	local top = self.path[#self.path]
	base.table.remove(self.path)
	self.receiver = nil
	self.level = self.level - 1
	return top
end

function getItemColor(self, item)
	--base.print('item.name = '..item.name)
	--base.print('item.color = '..base.tostring(item.color))
	if item.color then
		return item.color:get()
	else
		for index = #self.path, 1, -1 do
			local parentColor = self.path[index].color
			if parentColor ~= nil then
				return parentColor:get()
			end
		end
		return nil
	end
end

function makeGuiColor(color)
	local guiColorValue = {0, 0, 0}
	for i = 1, 3 do
		local bs = base.math.pow(256, 3 - i)
		local num = base.math.floor(color / bs)
		guiColorValue[i] = num / 256
		color = color - num * bs
	end
	return base.Color.new(guiColorValue[1], guiColorValue[2], guiColorValue[3])
end

function buildMenu_(self, menu)
	self.menu = menu
	
	local fullCaption = self.caption ~= nil and self.caption..'. ' or ''	
	if self.level > 1 then
		fullCaption = fullCaption..base.tostring(self.level)..'. '
	end
	for submenu_index, submenu_item in base.pairs(self.path) do
		if submenu_index > 1 then
			fullCaption = fullCaption..'. '..submenu_item.name
		else
			fullCaption = fullCaption..submenu_item.name
		end
	end
	self:setCaption(fullCaption)
	
	self.commandMenu:clear()	
	self.menuItemCommands = {}
	for index, item in base.pairs(menu.items) do
		if item.condition == nil or item.condition:check() then
			local item_name = item.name
			if item.submenu or item.menuBuilder then
				item_name = item_name..'...'
			else
				--item_name = item_name..'.'
			end
			local color = self:getItemColor(item) or (menu.color and menu.color:get()) or (self.color and self.color:get()) or utils.COLOR.WHITE			
			self.commandMenu:setItem(index, 'F'..index..'. '..item_name, makeGuiColor(color))
			self.menuItemCommands[index] = index
		end
	end	
	if self.level > 1 then
		self.commandMenu:setItem(11, _('F11. Previous Menu'), makeGuiColor(utils.COLOR.WHITE))
		self.menuItemCommands[11] = ON_PREVIOUS
	end
	self.commandMenu:setItem(12, _('F12. Exit'), makeGuiColor(utils.COLOR.WHITE))
	self.menuItemCommands[12] = ON_EXIT
end

function enterMenu_(self, menuItem)
	self:pushMenuItem_(menuItem)
	self:buildMenu_(menuItem.submenu)
end

function setMainMenu(self, menu)	
	self.fsm:onSymbol(ON_HOME)
	--base.print('mainMenu.name = '..base.tostring(self.mainMenu.name))
	--base.print('fsm.state = '..base.tostring(self.fsm.state))
end

function updateMenu(self)
	self:buildMenu_(self.menu)
end

function selectCommand_(self, menu_item)
	base.table.insert(self.path, menu_item)
	self.level = self.level + 1
	self:performCommands()
end

function selectMenuItem(self, menu_item_num)
	--base.print('CommandMenu.selectMenuItem('..base.tostring(menu_item_num)..')')
	if self.menuItemCommands then
		local inputSymbol = self.menuItemCommands[menu_item_num]
		--base.print('menu_item_num = '..base.tostring(menu_item_num))
		--base.print('inputSymbol = '..base.tostring(inputSymbol))
		--base.print('before: fsm.state = '..base.tostring(self.fsm.state))
		if inputSymbol then
			self.fsm:onSymbol(inputSymbol)
		end
	end
	if 	self.handler and
		self.handler.onDialogMenuItem then
		self.handler:onDialogMenuItem(menu_item_num)
	end
	--base.print('after: fsm.state = '..base.tostring(self.fsm.state))
end

function performCommands(self)
	local parameters = {}
	for path_index, path_item in base.pairs(self.path) do
		if path_item.command then
			self:onCommand(path_item.command, parameters)
			parameters = {}
		end	
		if path_item.parameter then
			base.table.insert(parameters, path_item.parameter)
		elseif path_item.parameters then
			for par_index, par_item in base.pairs(path_item.parameters) do
				base.table.insert(parameters, par_item)
			end
		end
	end
end

function onCommand(self, command, parameters)	
	--base.print('CommandMenu.onCommand()')
	self:closeMenu_()
	if command then
		if 	self.handler and
			self.handler.onDialogCommand then
			self.handler:onDialogCommand(self, command, parameters)
		end
	end
end

function setCaption(self, caption)
	self.menuCaption:setText(caption)
	local captionWidth, captionHeight = self.menuCaption:calcSize()
	self.menuCaption:setBounds(0, 0, menuWidth, captionHeight)
	local menuX, menuY, menuWidth, menuHeight = self.commandMenu:getContainer():getBounds()
	self.commandMenu:getContainer():setBounds(0, captionHeight + 5, menuWidth, menuHeight)
	if 	self.handler and
		self.handler.onDialogSetCaption then
		self.handler:onDialogSetCaption(self, caption)
	end
end

function clearMenu(self)
	self.path = {}
	self.level = 0
	self.commandMenu:clear()
	self:setCaption('')
end

function closeMenu_(self)
	self:setMainMenu()
	self:toggleMenu(false)
end

local addTimeStamp = false

local function getTimeStr(timeSec)
	return base.string.format('[%02d:%02d:%02d]', utils.getTime(timeSec))
end

local function pushMessageBack(self, msg, color, disappearTime)
	base.table.insert(self.messageDisappearTimeQueue, disappearTime)
	self.messageLog:insertItem(nil, addTimeStamp and getTimeStr(base.timer.getAbsTime())..' '..msg or msg, makeGuiColor(color))
end

local function popMessageFront(self)
	base.table.remove(self.messageDisappearTimeQueue, 1)
	self.messageLog:deleteItem(1)
end

local function popMessageFunc(self, time)
	local dtime = 0.1
	popMessageFront(self)
	while 	#self.messageDisappearTimeQueue > 0 and
			self.messageDisappearTimeQueue[1] <= time + dtime do
		popMessageFront(self)
		base.assert(#self.messageDisappearTimeQueue == self.messageLog:getItemCount())
	end
	if #self.messageDisappearTimeQueue > 0 then
		return self.messageDisappearTimeQueue[1]
	else
		self.funcRef = nil
	end
	if self:isLogEmpty() then
		if 	self.handler and
			self.handler.onCommandMenuEmpty then
			self.handler:onCommandMenuEmpty(self)
		end
	end
end

function onMsg(self, msg, color, duration)
	local disappearTime = base.timer.getTime() + duration
	local oldMessageLogSize = self.messageLog:getItemCount()
	base.assert((oldMessageLogSize == 0) == (self.funcRef == nil))
	pushMessageBack(self, msg, color, disappearTime)
	
	local messageLogSize = self.messageLog:getItemCount()
	local itemsToRemove = oldMessageLogSize + 1 - messageLogSize	
	for i = 1, itemsToRemove do
		base.table.remove(self.messageDisappearTimeQueue, 1)
	end
	
	if self.funcRef == nil then
		if #self.messageDisappearTimeQueue > 0 then
			self.funcRef = base.timer.scheduleFunction(popMessageFunc, self, self.messageDisappearTimeQueue[1])
		end
	else
		if #self.messageDisappearTimeQueue > 0 then
			base.timer.setFunctionTime(self.funcRef, self.messageDisappearTimeQueue[1])
		else
			base.timer.removeFunction(self.funcRef)
			self.funcRef = nil
		end
	end	
	base.assert(#self.messageDisappearTimeQueue == self.messageLog:getItemCount())
end

--fsm handler
	
function onSymbol(self, outputSymbol)
	if outputSymbol then
		outputSymbol(self)
	end
end

--C++ event handlers

function onMenuItem(self, menu_item_num)
	--base.print('CommandMenu.onMenuItem('..base.tostring(menu_item_num)..')')
	self:selectMenuItem(menu_item_num)
end

function onMenuToggle(self, on)
	--base.print('CommandMenu.onMenuToggle('..base.tostring(on)..')')
	if self.fsm then
		if on then
			self.fsm:onSymbol(ON_OPEN)
		else
			self.fsm:onSymbol(ON_CLOSE)
		end
	end
	if 	self.handler and
		self.handler.onDialogToggle then
		self.handler:onDialogToggle(on)
	end
	return true
end

function getContainer(self)
	return self.container
end

list = {}

handlers = {
	['onMenuItem'] 		= onMenuItem,
	['onMenuToggle']	= onMenuToggle
}
